client_id <- readLines("spotify_client_id.txt")
## Warning in readLines("spotify_client_id.txt"): incomplete final line found on
## 'spotify_client_id.txt'
client_secret <- readLines("spotify_client_secret.txt")
## Warning in readLines("spotify_client_secret.txt"): incomplete final line found
## on 'spotify_client_secret.txt'
get_spotify_token <- function(client_id, client_secret) {
  resp <- request("https://accounts.spotify.com/api/token") |>
    req_auth_basic(client_id, client_secret) |>
    req_body_form(grant_type = "client_credentials") |>
    req_method("POST") |>
    req_perform()
  
  resp_body_json(resp)$access_token
}

token <- get_spotify_token(client_id, client_secret)
get_artists_data <- function(artist_ids, token) {
  artist_chunks <- split(artist_ids, ceiling(seq_along(artist_ids) / 50))
  
  map_dfr(artist_chunks, function(ids_chunk) {
    req <- request("https://api.spotify.com/v1/artists") |>
      req_url_query(ids = paste(ids_chunk, collapse = ",")) |>
      req_headers(Authorization = paste("Bearer", token))
    
    resp <- req_perform(req)
    content <- resp_body_json(resp, simplifyVector = FALSE)
    
    map_dfr(content$artists, function(artist) {
      tibble(
        artist_id = artist$id,
        artist_name = artist$name,
        genres = paste(artist$genres, collapse = ", "),
        popularity = artist$popularity,
        followers = artist$followers$total,
        artist_url = artist$external_urls$spotify
      )
    })
  })
}

get_artist_top_tracks <- function(artist_id, token) {
  url <- paste0("https://api.spotify.com/v1/artists/", artist_id, "/top-tracks?market=US")
  
  req <- request(url) |>
    req_headers(Authorization = paste("Bearer", token))
  
  resp <- req_perform(req)
  content <- resp_body_json(resp, simplifyVector = FALSE)
  
  map_dfr(content$tracks, function(track) {
    tibble(
      track_name = track$name,
      album_name = track$album$name,
      popularity = track$popularity,
      track_duration_ms = track$duration_ms,
      explicit = track$explicit,
      artist_id = artist_id
    )
  })
}

artist_ids <- c(
  "06HL4z0CvFAxyc27GXpf02",  # Taylor Swift
  "1Xyo4u8uXC1ZmMpatF05PJ",  # The Weeknd
  "4q3ewBCX7sLwd24euuV69X",  # Bad Bunny
  "3TVXtAsR1Inumwj472S9r4",  # Drake
  "6qqNVTkY8uBg9cP3Jd7DAH",  # Billie Eilish
  "0Y5tJX1MQlPlqiwlOH1tJY",  # Travis Scott
  "12GqGscKJx3aE4t07u7eVZ",  # Peso Pluma
  "5K4W6rqBFWDnAN6FQUkS6x",  # Kanye West
  "66CXWjxzNUsdJxJ2JdwvnR",  # Ariana Grande
  "2LRoIwlKmHjgvigdNGBHNo"   # Feid
)

artists_data <- get_artists_data(artist_ids, token)
all_tracks <- map_dfr(artist_ids, get_artist_top_tracks, token = token)

# Merge track data with artist names
all_tracks <- left_join(all_tracks, artists_data |> select(artist_id, artist_name), by = "artist_id")

Top Albums

# Collecting IDs for Top Albums from 2018 to 2024

album_ids_2018 <- c(
  "1ATL5GLyefJaxhQzSPVrLX", # Scorpion - Drake
  "6trNtQUgC8cgbWcqoMYkOR", # beerbons & bentleys - Post Malone
  "2Ti79nwTsont5ZHfdxIzAm", # ? - XXXTENTACION
  "2vlhlrgMaXqcnhRqIEV9AP", # Dua Lipa - Dua Lipa
  "3T4tUhGYeRNVUGevb0wThu"  # ÷ - Ed Sheeran
)

album_ids_2019 <- c(
  "0S0KGZnfBGSIssfF54WSJh", # WHEN WE ALL FALL ASLEEP, WHERE DO WE GO? – Billie Eilish
  "4g1ZRSobMefqF6nelkgibi", # Hollywood’s Bleeding – Post Malone
  "2fYhqwDWXjbpjaIJPEfKFw", # thank u, next – Ariana Grande
  "3oIFxDIo2fwuk4lwCmFZCx", # No.6 Collaborations Project – Ed Sheeran
  "0xzScN8P3hQAz3BT3YYX5w"  # Shawn Mendes – Shawn Mendes
)

album_ids_2020 <- c(
  "5lJqux7orBlA1QzyiBGti1", # YHLQMDLG - Bad Bunny
  "4yP0hdKOZPNshxUOjY0cZj", # After Hours - The Weeknd
  "4g1ZRSobMefqF6nelkgibi", # Hollywood's Bleeding - Post Malone
  "7xV2TzoaVc0ycW7fwBwAml", # Fine Line - Harry Styles
  "7fJJK56U9fHixgO0HQkhtI"  # Future Nostalgia - Dua Lipa
)

album_ids_2021 <- c(
  "6s84u2TUpR3wdUv4NgKA2j", # SOUR - Olivia Rodrigo
  "7fJJK56U9fHixgO0HQkhtI", # Future Nostalgia - Dua Lipa
  "5dGWwsZ9iB2Xc3UKR0gif2", # Justice - Justin Bieber
  "32iAEBstCjauDhyKpGjTuq", # = - Ed Sheeran
  "4XLPYMERZZaBzkJg0mkdvO"  # Planet Her - Doja Cat
)

album_ids_2022 <- c(
  "3RQQmkQEvNCY4prGKE6oc5", # Un Verano Sin Ti - Bad Bunny
  "5r36AJ6VOJtp00oxSkBZ5h", # Harry's House - Harry Styles
  "6s84u2TUpR3wdUv4NgKA2j", # SOUR - Olivia Rodrigo
  "32iAEBstCjauDhyKpGjTuq", # = - Ed Sheeran
  "4XLPYMERZZaBzkJg0mkdvO"  # Planet Her - Doja Cat
)

album_ids_2023 <- c(
  "3RQQmkQEvNCY4prGKE6oc5", # Un Verano Sin Ti - Bad Bunny
  "151w1FgRZfnKZA9FEcg9Z3", # Midnights - Taylor Swift
  "07w0rG5TETcyihsEIZR3qG", # SOS - SZA
  "2ODvWsOgouMbaA5xf0RkJe", # Starboy - The Weeknd
  "4kS7bSuU0Jm9LYMosFU2x5", # MAÑANA SERÁ BONITO - KAROL G
  "6i7mF7whyRJuLJ4ogbH2wh", # One Thing at a Time - Morgan Wallen
  "1NAmidJlEaVgA3MpcPFYGq", # Lover - Taylor Swift
  "7txGsnDSqVMoRl6RQ9XyZP", # HEROES & VILLAINS - Metro Boomin
  "4jox3ip1I39DFC2B7R5qLH", # GÉNESIS - Peso Pluma
  "5r36AJ6VOJtp00oxSkBZ5h"  # Harry's House - Harry Styles
)

album_ids_2024 <- c(
  "5H7ixXZfsNMGbIE5OBSpcb", # THE TORTURED POETS DEPARTMENT: THE ANTHOLOGY - Taylor Swift 
  "7aJuG4TFXa2hmE4z1yxc3n", # HIT ME HARD AND SOFT - Billie Eilish 
  "3iPSVi54hsacKKl1xIR2eH", # Short n’ Sweet - Sabrina Carpenter 
  "4kS7bSuU0Jm9LYMosFU2x5", # MAÑANA SERÁ BONITO - Karol G
  "5EYKrEDnKhhcNxGedaRQeK", # eternal sunshine - Ariana Grande
  "64LU4c1nfjz1t4VnGhagcg", # 1989 (Taylor’s Version) - Taylor Swift
  "07w0rG5TETcyihsEIZR3qG", # SOS - SZA
  "1NAmidJlEaVgA3MpcPFYGq", # Lover - Taylor Swift
  "168CdR21lfn0TTyw1Pkdcm", # Fireworks & Rollerblades - Benson Boone
  "2ODvWsOgouMbaA5xf0RkJe"  # Starboy - The Weeknd  
)
# Collecting album data for a given year via function
get_album_data <- function(album_ids, token) {
  album_chunks <- split(album_ids, ceiling(seq_along(album_ids) / 50))
  
  map_dfr(album_chunks, function(ids_chunk) {
    req <- request("https://api.spotify.com/v1/albums") |>
      req_url_query(ids = paste(ids_chunk, collapse = ",")) |>
      req_headers(Authorization = paste("Bearer", token))
    
    resp <- req_perform(req)
    content <- resp_body_json(resp, simplifyVector = FALSE)
    
    map_dfr(content$albums, function(album) {
      tibble(
        id = album$id,
        name = album$name,
        release_date = album$release_date,
        artists = paste(album$artists, collapse = ", "),
      )
    })
  })
}

# Assigning data from previous function into a table
album_data_2024 <- get_album_data(album_ids_2024, token)
get_album_tracks <- function(album_id, token) {
  
  url <- paste0("https://api.spotify.com/v1/albums/", album_id, "/tracks")
  
  req <- GET(
    url,
    add_headers(Authorization = paste("Bearer", token))
  )
  
  content <- content(req, "parsed", simplifyVector = FALSE)

  table <- map_dfr(content$items, function(track) {
    tibble(
      album_id = album_id,
      track_id = track$id,
      track_name = track$name,
      track_number = track$track_number,
      track_artists = paste(map_chr(track$artists, "name"), collapse = ", "),
      track_duration_ms = track$duration_ms,
      track_explicit = track$explicit
    )
  })
}


all_albums <- map_dfr(album_ids_2018, get_album_tracks, token = token)

# Merge track data with artist names
#all_tracks <- left_join(all_tracks, album_data |> select(artist_id, artist_name), by = "artist_id")

Popularity by Track Name (Color by Explicit)

all_tracks |>
  filter(artist_name == "Taylor Swift") |>
  plot_ly(
        x = ~reorder(track_name, popularity),
        y = ~popularity,
        type = 'bar',
        color = ~explicit,
        colors = c('blue', 'red'),
        text = ~paste("Track:", track_name,
                      "\nArtist:", artist_name,
                      "\nPopularity:", popularity,
                      "\nExplicit:", explicit),
        hoverinfo = "text") |>
  layout(title = "Top Tracks by Popularity",
         xaxis = list(title = "Track Name", tickangle = -45),
         yaxis = list(title = "Popularity"))

Resources:

  1. Revealed: The Top Artists, Songs, Albums, Podcasts, and Audiobooks of 2024 (https://newsroom.spotify.com/2024-12-04/top-songs-artists-podcasts-audiobooks-albums-trends-2024/)

  2. Spotify Web API (https://developer.spotify.com/documentation/web-api)